//=== unittests/CodeGen/IncrementalProcessingTest.cpp - IncrementalCodeGen ===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "TestCompiler.h"

#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/CodeGen/ModuleBuilder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Parse/Parser.h"
#include "clang/Sema/Sema.h"
#include "llvm/ADT/Triple.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/MemoryBuffer.h"
#include "gtest/gtest.h"

#include <memory>

using namespace llvm;
using namespace clang;

namespace {

// Incremental processing produces several modules, all using the same "main
// file". Make sure CodeGen can cope with that, e.g. for static initializers.
const char TestProgram1[] =
    "extern \"C\" int funcForProg1() { return 17; }\n"
    "struct EmitCXXGlobalInitFunc1 {\n"
    "   EmitCXXGlobalInitFunc1() {}\n"
    "} test1;";

const char TestProgram2[] =
    "extern \"C\" int funcForProg2() { return 42; }\n"
    "struct EmitCXXGlobalInitFunc2 {\n"
    "   EmitCXXGlobalInitFunc2() {}\n"
    "} test2;";


/// An incremental version of ParseAST().
static std::unique_ptr<llvm::Module>
IncrementalParseAST(CompilerInstance& CI, Parser& P,
                    CodeGenerator& CG, const char* code) {
  static int counter = 0;
  struct IncreaseCounterOnRet {
    ~IncreaseCounterOnRet() {
      ++counter;
    }
  } ICOR;

  Sema& S = CI.getSema();
  clang::SourceManager &SM = S.getSourceManager();
  if (!code) {
    // Main file
    SM.setMainFileID(SM.createFileID(
        llvm::MemoryBuffer::getMemBuffer("    "), clang::SrcMgr::C_User));

    S.getPreprocessor().EnterMainSourceFile();
    P.Initialize();
  } else {
    FileID FID = SM.createFileID(
        llvm::MemoryBuffer::getMemBuffer(code), clang::SrcMgr::C_User);
    SourceLocation MainStartLoc = SM.getLocForStartOfFile(SM.getMainFileID());
    SourceLocation InclLoc = MainStartLoc.getLocWithOffset(counter);
    S.getPreprocessor().EnterSourceFile(FID, 0, InclLoc);
  }

  ExternalASTSource *External = S.getASTContext().getExternalSource();
  if (External)
    External->StartTranslationUnit(&CG);

  Parser::DeclGroupPtrTy ADecl;
  for (bool AtEOF = P.ParseFirstTopLevelDecl(ADecl); !AtEOF;
       AtEOF = P.ParseTopLevelDecl(ADecl)) {
    // If we got a null return and something *was* parsed, ignore it.  This
    // is due to a top-level semicolon, an action override, or a parse error
    // skipping something.
    if (ADecl && !CG.HandleTopLevelDecl(ADecl.get()))
      return nullptr;
  }

  // Process any TopLevelDecls generated by #pragma weak.
  for (Decl *D : S.WeakTopLevelDecls())
    CG.HandleTopLevelDecl(DeclGroupRef(D));

  CG.HandleTranslationUnit(S.getASTContext());

  std::unique_ptr<llvm::Module> M(CG.ReleaseModule());
  // Switch to next module.
  CG.StartModule("incremental-module-" + std::to_string(counter),
                 M->getContext());
  return M;
}

const Function* getGlobalInit(llvm::Module& M) {
  for (const auto& Func: M)
    if (Func.hasName() && Func.getName().startswith("_GLOBAL__sub_I_"))
      return &Func;

  return nullptr;
}

TEST(IncrementalProcessing, EmitCXXGlobalInitFunc) {
    clang::LangOptions LO;
    LO.CPlusPlus = 1;
    LO.CPlusPlus11 = 1;
    TestCompiler Compiler(LO);
    clang::CompilerInstance &CI = Compiler.compiler;
    CI.getPreprocessor().enableIncrementalProcessing();
    CI.setASTConsumer(std::move(Compiler.CG));
    clang::CodeGenerator& CG =
      static_cast<clang::CodeGenerator&>(CI.getASTConsumer());
    CI.createSema(clang::TU_Prefix, nullptr);

    Sema& S = CI.getSema();

    std::unique_ptr<Parser> ParseOP(new Parser(S.getPreprocessor(), S,
                                               /*SkipFunctionBodies*/ false));
    Parser &P = *ParseOP.get();

    std::array<std::unique_ptr<llvm::Module>, 3> M;
    M[0] = IncrementalParseAST(CI, P, CG, nullptr);
    ASSERT_TRUE(M[0]);

    M[1] = IncrementalParseAST(CI, P, CG, TestProgram1);
    ASSERT_TRUE(M[1]);
    ASSERT_TRUE(M[1]->getFunction("funcForProg1"));

    M[2] = IncrementalParseAST(CI, P, CG, TestProgram2);
    ASSERT_TRUE(M[2]);
    ASSERT_TRUE(M[2]->getFunction("funcForProg2"));
    // First code should not end up in second module:
    ASSERT_FALSE(M[2]->getFunction("funcForProg1"));

    // Make sure global inits exist and are unique:
    const Function* GlobalInit1 = getGlobalInit(*M[1]);
    ASSERT_TRUE(GlobalInit1);

    const Function* GlobalInit2 = getGlobalInit(*M[2]);
    ASSERT_TRUE(GlobalInit2);

    ASSERT_FALSE(GlobalInit1->getName() == GlobalInit2->getName());

}

} // end anonymous namespace
